﻿///***************************************************************
/// 시  스  템 : SMFramework (based on Microsoft.Framework)
/// 단위시스템 : Adapter
/// 계      층 : Adapter
/// 프로그램명 : FtpClient
/// 파  일  명 : FtpClient.cs
/// 설      명 : FTP관련 라이브러리
///***************************************************************
/// 수정ID 일자       이름     내용
///---------------------------------------------------------------
/// CH001  2010-07-15 송시명   최초 작성
///***************************************************************
using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Text;
using SMFramework.Diagnostics;

namespace SMFramework.Adapter
{
	/// <summary>
	/// FTP관련 라이브러리
	/// </summary>
	public class FtpClient
	{
		#region Variables Declaration
		
		private static Encoding ASCII = Encoding.ASCII;

		private string hostName = "";
		private string path = ".";
		private string userID = "";
		private string password = "";
		private string message = null;
		private string result = null;

		private int portNr = 21;
		private int bytes = 0;
		private int resultCode = 0;

		private bool loggedin = false;
		private bool binMode = false;

		private int timeOut = 20;
		private int maxBufferSize = 49152;

		private Byte[] buffer;
		private Socket ftpClientSocket = null;
		
		#endregion
		
		#region Property Declaration

		/// <summary>
		/// Gets and Sets the name of the FTP hostName.
		/// </summary>
		/// <returns></returns>
		public string RemoteHostName
		{
			get
			{
				return this.hostName;
			}
			set
			{
				this.hostName = value;
			}
		}
		/// <summary>
		/// Remote hostName portNr. Typically TCP 21
		/// </summary>
		public int RemotePortNr
		{
			get
			{
				return this.portNr;
			}
			set
			{
				this.portNr = value;
			}
		}
		/// <summary>
		/// Gets and Sets the remote directory.
		/// </summary>
		public string RemotePath
		{
			get
			{
				return this.path;
			}
			set
			{
				this.path = value;
			}

		}
		/// <summary>
		/// Gets and Sets the userID.
		/// </summary>
		public string UserID
		{
			get
			{
				return this.userID;
			}
			set
			{
				this.userID = value;
			}
		}
		/// <summary>
		/// Gets and Set the password.
		/// </summary>
		public string Password
		{
			get
			{
				return this.password;
			}
			set
			{
				this.password = value;
			}
		}
		/// <summary>
		/// Timeout waiting for a response from hostName, in seconds.
		/// </summary>
		public int Timeout
		{
			get
			{
				return this.timeOut;
			}
			set
			{
				this.timeOut = value;
			}
		}

		/// <summary>
		/// MaxBufferSize
		/// </summary>
		public int MaxBufferSize
		{
			get
			{
				return this.maxBufferSize;
			}
			set
			{
				this.maxBufferSize = value;
			}
		}
		/// <summary>
		/// If the value of mode is true, set binary mode for downloads, else, Ascii mode.
		/// </summary>
		public bool BinaryMode
		{
			get
			{
				return this.binMode;
			}
			set
			{
				if (this.binMode != value)
				{
					if (value) 
					{
						sendCommand("TYPE I");
					}
					else 
					{
						sendCommand("TYPE A");
					}

					if (this.resultCode != 200) 
					{
						throw new Exception(result.Substring(4));
					}

					this.binMode = value;
				}
			}
		}
		#endregion

		#region Default Constuctor & Overrided Constructor
		/// <summary>
		/// FtpClient 생성자
		/// </summary>
		public FtpClient()
		{
		}

		/// <summary>
		/// FtpClient 생성자(hostName, userID, password)
		/// </summary>
		/// <param name="hostName"></param>
		/// <param name="userID"></param>
		/// <param name="password"></param>
		public FtpClient(string hostName, string userID, string password)
		{
			this.hostName = hostName;
			this.userID = userID;
			this.password = password;
		}

		/// <summary>
		/// FtpClient 생성자(hostName, portNr, userID, password, timeOut)
		/// </summary>
		/// <param name="hostName"></param>
		/// <param name="portNr"></param>
		/// <param name="userID"></param>
		/// <param name="password"></param>
		/// <param name="timeOut"></param>
		public FtpClient(string hostName, int portNr, string userID, string password, int timeOut)
		{
			this.hostName = hostName;
			this.portNr = portNr;
			this.userID = userID;
			this.password = password;
			this.timeOut = timeOut;	
		}
		#endregion

		#region Connection & Closing
		/// <summary>
		/// 로그인
		/// </summary>
		public void Login()
		{
			IPHostEntry ipHostInfo = null;
			IPAddress ipAddress = null;
			IPEndPoint ipEndPoint = null;			

			buffer = new Byte[maxBufferSize];

			try
			{
				ipHostInfo = Dns.GetHostEntry(this.hostName);
				ipAddress = ipHostInfo.AddressList[0];
				ipEndPoint = new IPEndPoint(ipAddress, this.portNr);

				this.ftpClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
				this.ftpClientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, this.timeOut);
				this.ftpClientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, this.timeOut);

				this.ftpClientSocket.Connect(ipEndPoint);

				this.readResponse();
				if (this.resultCode != 220)
				{
					throw new Exception(this.result.Substring(4));
				} 
				else 
				{
					this.sendCommand("USER " + userID);
					if (this.resultCode != 331 && this.resultCode != 230)
					{
						throw new Exception(this.result.Substring(4));
					}
					else if (this.resultCode == 331)
					{
						this.sendCommand("PASS " + password);
						if (this.resultCode != 230 && this.resultCode != 202)
						{
							throw new Exception(this.result.Substring(4));
						}
					}
				}

				loggedin = true;
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		
		/// <summary>
		/// 연결해제
		/// </summary>
		public void Close()
		{
			if (this.ftpClientSocket != null)
			{
				if (this.ftpClientSocket.Connected) 
				{
					if (loggedin) 
					{
						this.sendCommand("QUIT");
					}

					this.ftpClientSocket.Shutdown(SocketShutdown.Both);
					this.ftpClientSocket.Close();
				}

				this.ftpClientSocket = null;
			}
		}
		#endregion

		#region Commands

		#region GetFileSize

		/// <summary>
		/// 파일크기 리턴
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns></returns>
		public long GetFileSizeInfo(string fileName)
		{
			return GetFileSize(fileName);
		}

		/// <summary>
		/// 파일크기 리턴
		/// </summary>
		/// <param name="fileName"></param>
		/// <returns></returns>
		private long GetFileSize(string fileName)
		{
			long size = 0;

			try 
			{
				this.sendCommand("SIZE " + fileName);
				if (this.resultCode == 213) 
				{
					size = long.Parse(this.result.Substring(4));
				}
			}
			catch (Exception ex) 
			{
				throw ex;
			}

			return size;
		}
		#endregion
		
		#region GetFileList

		/// <summary>
		/// 파일존재여부
		/// </summary>
		/// <param name="mask"></param>
		/// <returns></returns>
		public bool IsFileExist(string mask)
		{
			string [] fileList = GetFileList(mask);
			return (fileList.Length > 1);	//빈 값 포함되므로
		}

		/// <summary>
		/// 디렉토리내 모든 파일의 이름|크기 리턴
		/// </summary>
		/// <returns></returns>
		public string[] GetFileInfo()
		{
			return GetFileInfo("*.*");
		}

		/// <summary>
		/// 디렉토리내 특정 확장자 파일의 이름|크기 리턴
		/// </summary>
		/// <param name="mask"></param>
		/// <returns></returns>
		public string[] GetFileInfo(string mask)
		{
			string[] fileList = GetFileList(mask);

			if (fileList.Length > 1)
			{
				for (int i = 0; i < fileList.Length - 1; i++)
				{
					fileList[i] = fileList[i] + "|" + GetFileSizeInfo(fileList[i]);
				}
			}

			return fileList;
		}

		/// <summary>
		/// 디렉토리내 모든 파일의 이름 리턴
		/// </summary>
		/// <returns></returns>
		public string[] GetFileNames()
		{
			return GetFileList("*.*");
		}

		/// <summary>
		/// 디렉토리내 특정 확장자 파일의 이름 리턴
		/// </summary>
		/// <param name="mask"></param>
		/// <returns></returns>
		public string[] GetFileNames(string mask)
		{
			return GetFileList(mask);
		}

		private string[] GetFileList(string mask)
		{
			string[] fileList;
		
			int bytes;
			DateTime timeout;

			Socket eFTPDataSocketClient = null;

			try 
			{
				eFTPDataSocketClient = createDataSocket();

				this.sendCommand("NLST " + mask);
				if (this.resultCode != 150 && this.resultCode != 125)
				{
					throw new Exception(this.result.Substring(4));
				}

				this.message = "";
				timeout = DateTime.Now.AddSeconds(this.timeOut);

				while (timeout > DateTime.Now)
				{
					bytes = eFTPDataSocketClient.Receive(this.buffer, this.buffer.Length, 0);
					this.message = this.message + ASCII.GetString(this.buffer, 0, bytes);

					if (bytes < this.buffer.Length) 
					{
						break;
					}
				}

				fileList = this.message.Replace("\r","").Split('\n');
				if (this.message.IndexOf("No such file or directory") != -1) 
				{
					fileList = new string[]{};
				}				


				this.readResponse();
				if (this.resultCode != 226) 
				{
					fileList = new string[]{};
				}				
			}
			catch (Exception ex) 
			{
				throw ex;
			}
			finally 
			{
				if (eFTPDataSocketClient != null)
				{
					if (eFTPDataSocketClient.Connected) 
					{
						eFTPDataSocketClient.Shutdown(SocketShutdown.Both);
						eFTPDataSocketClient.Close();
					}

					eFTPDataSocketClient = null;
				}
			}

			return fileList;
		}
		#endregion

		#region GetDirList
		/// <summary>
		/// 디렉토리 정보
		/// </summary>
		/// <param name="remDir"></param>
		/// <returns></returns>
		private string[] GetDirList(string remDir)
		{
			string[] dirList;
			string[] fileList;

			int bytes;
			DateTime timeout;

			Socket eFTPDataSocketClient = null;

			try 
			{
				eFTPDataSocketClient= createDataSocket();

				this.sendCommand("LIST " + remDir);
				if (this.resultCode != 150 && this.resultCode != 125) 
				{
					throw new Exception(this.result.Substring(4));
				}			

				this.message = "";
				timeout = DateTime.Now.AddSeconds(this.timeOut);

				while (timeout > DateTime.Now)
				{
					bytes = eFTPDataSocketClient.Receive(this.buffer, this.buffer.Length, 0);
					this.message += ASCII.GetString(this.buffer, 0, bytes);

					if (bytes < this.buffer.Length) 
					{
						break;
					}
				}

				fileList = this.message.Replace("\r","").Split('\n');
				if (this.message.IndexOf( "No such file or directory" ) != -1) 
				{
					fileList = new string[]{};
				}				

				this.readResponse();
				if (this.resultCode != 226) 
				{
					fileList = new string[]{};
				}				

				dirList = new string[fileList.Length];
				for (int index=0;index<fileList.Length;index++) 
				{
					if (fileList[index].Replace(">", "").Length != fileList[index].Length) 
					{
						dirList[index] = fileList[index].Replace(" ", "").Split('>')[1];;
					}		
				}
			}
			catch (Exception ex) 
			{
				throw ex;
			}
			finally 
			{
				if (eFTPDataSocketClient != null)
				{
					if (eFTPDataSocketClient.Connected) 
					{
						eFTPDataSocketClient.Shutdown(SocketShutdown.Both);
						eFTPDataSocketClient.Close();
					}

					eFTPDataSocketClient = null;
				}
			}

			return dirList;
		}
		#endregion

		#region DownloadFile
		/// <summary>
		/// 파일 다운로드
		/// </summary>
		/// <param name="remFileName"></param>
		/// <param name="locDir"></param>
		/// <param name="locTempDir"></param>
		/// <param name="locFileName"></param>
		/// <param name="resume"></param>
		public void DownloadFile(string remFileName, string locDir, string locTempDir, string locFileName, bool resume)
		{
			string locPath;
			long offset;
			DateTime timeout;
			
			FileStream output = null;
			Socket eFTPDataSocketClient = null;

			this.BinaryMode = true;

			try 
			{
				eFTPDataSocketClient = createDataSocket();

				#region locPath Setting
				if (locFileName.Trim().Equals(""))
				{
					locFileName = remFileName;
				}
				locPath = locTempDir + @"\" + locFileName;
				#endregion

				#region Open file to write and Resuming
				if (!File.Exists(locPath)) 
				{
					output = File.Create(locPath);
				}
				else 
				{
					output = new FileStream(locPath, FileMode.Open);
				}		
								
				offset = 0;
				if (resume)
				{
					offset = output.Length;

					if ( offset > 0 )
					{
						this.sendCommand( "REST " + offset );
						if (this.resultCode != 350)
						{
							offset = 0;
						}
						else
						{
							output.Seek(offset, SeekOrigin.Begin);
						}
					}
				}	
				#endregion

				this.sendCommand("RETR " + remFileName);
				if (this.resultCode != 150 && this.resultCode != 125)
				{
					throw new Exception(this.result.Substring(4));
				}

				timeout = DateTime.Now.AddSeconds(this.timeOut);
				while (timeout > DateTime.Now)
				{
					this.bytes = eFTPDataSocketClient.Receive(this.buffer, this.buffer.Length, 0);
					output.Write(this.buffer,0,this.bytes);

					if (this.bytes <= 0)
					{
						break;
					}
				}

				this.readResponse();
				if(this.resultCode != 226 && this.resultCode != 250) 
				{
					throw new Exception(this.result.Substring(4));
				}	

				if (output != null) 
				{
					output.Close();
					output = null;
				}
		
				//System.IO.File.Copy(locPath, locDir + @"\" + locFileName, true);
				//System.IO.File.Delete(locPath);
			}
			catch (Exception ex) 
			{
				throw ex;
			}
			finally 
			{
				if (output != null) 
				{
					output.Close();
					output = null;
				}

				if (eFTPDataSocketClient != null)
				{
					if (eFTPDataSocketClient.Connected) 
					{
						eFTPDataSocketClient.Shutdown(SocketShutdown.Both);
						eFTPDataSocketClient.Close();
					}

					eFTPDataSocketClient = null;
				}
			}
		}
		#endregion

		#region DownloadFiles
		/// <summary>
		/// 파일 다운로드
		/// </summary>
		/// <param name="mask"></param>
		/// <param name="locDir"></param>
		/// <param name="locTempDir"></param>
		public void DownloadFiles(string mask, string locDir, string locTempDir)
		{
			try 
			{
				foreach (string file in this.GetFileList(mask))
				{
					if (file.Trim() != "")
					{
						this.DownloadFile(file, locDir, locTempDir, "", true);
					}				
				}
			}
			catch (Exception ex) 
			{
				throw ex;
			}
		}
		#endregion

		#region DownloadDirectory
		/// <summary>
		/// 디렉토리 다운로드
		/// </summary>
		/// <param name="remDir"></param>
		/// <param name="locDir"></param>
		/// <param name="locTempDir"></param>
		/// <param name="recursive"></param>
		public void DownloadDirectory(string remDir, string locDir, string locTempDir, bool recursive)
		{
			try 
			{
				this.DownloadFiles("*.*", locDir, locTempDir);

				if (recursive)
				{
					foreach (string directory in this.GetDirList(remDir))
					{
						if (directory != null) 
						{
							this.DownloadDirectory(directory, locDir, locTempDir, recursive);
						}					
					}

					this.ChangeDir("..");
				}				
			}
			catch (Exception ex) 
			{
				throw ex;
			}
		}
		#endregion

		#region UploadOnlyFile
		
		/// <summary>
		/// 여러 디렉토리내에서 파일만 업로드
		/// </summary>
		/// <param name="locDir"></param>
		/// <param name="locFileName"></param>
		/// <param name="remDir"></param>
		/// <param name="remote_temp_dir"></param>
		/// <param name="resume"></param>
		public void UploadOnlyFile(string locDir, string locFileName, string remDir, string remote_temp_dir, bool resume)
		{
			string locPath;
			long offset = 0;

			FileStream input = null;
			Socket eFTPDataSocketClient = null;

			this.BinaryMode = true;

			try 
			{

				eFTPDataSocketClient = createDataSocket();

				#region locPath Setting
				if (locDir.Trim() != "") 
				{
					locPath = locDir + @"\" + locFileName;
				}
				else 
				{
					locPath = locFileName;
				}
				#endregion

				#region open stream to read file and Resuming
				input = new FileStream(locPath, FileMode.Open);

				if (resume)
				{				
					offset = GetFileSize(Path.GetFileName(locPath));

					if (input.Length < offset) 
					{
						offset = 0;// different file size
					}
					else if (input.Length == offset) 
					{
						input.Close();// file done
						return;
					}
				}	
				
				if (offset > 0)
				{
					this.sendCommand("REST " + offset);
					if (this.resultCode != 350)
					{
						offset = 0;
					}
				}		
				#endregion

				this.sendCommand("STOR " + Path.GetFileName(locPath));
				if (this.resultCode != 125 && this.resultCode != 150) 
				{
					throw new Exception(result.Substring(4));
				}

				if (offset != 0)
				{
					input.Seek(offset,SeekOrigin.Begin);
				}

				while ((bytes = input.Read(this.buffer, 0, this.buffer.Length)) > 0)
				{
					eFTPDataSocketClient.Send(this.buffer, bytes, 0);
				}

				if (eFTPDataSocketClient.Connected)
				{
					eFTPDataSocketClient.Close();
				}
			
				this.readResponse();
				if (this.resultCode != 226 && this.resultCode != 250) 
				{
					throw new Exception(this.result.Substring(4));
				}

				//RenameFile(Path.GetFileName(locPath), ".." + @"\" + Path.GetFileName(locPath), true);
				//System.IO.File.Delete(locPath);
			}
			catch (Exception ex) 
			{
				throw ex;
			}
			finally 
			{
				if (input != null) 
				{
					input.Close();
					input = null;
				}

				if (eFTPDataSocketClient != null)
				{
					if (eFTPDataSocketClient.Connected) 
					{
						eFTPDataSocketClient.Shutdown(SocketShutdown.Both);
						eFTPDataSocketClient.Close();
					}

					eFTPDataSocketClient = null;
				}
			}
		}

		#endregion

		#region UploadOnlyFiles
		/// <summary>
		/// 여러 디렉토리내에서 파일만 업로드
		/// </summary>
		/// <param name="locDir"></param>
		/// <param name="mask"></param>
		/// <param name="remDir"></param>
		/// <param name="remote_temp_dir"></param>
		public void UploadOnlyFiles(string locDir, string mask, string remDir, string remote_temp_dir)
		{
			try 
			{
				foreach (string locFileName in Directory.GetFiles(locDir, mask))
				{
					this.UploadOnlyFile("", locFileName, remDir, remote_temp_dir, false);
				}
			}
			catch (Exception ex) 
			{
				throw ex;
			}
		}

		#endregion

		#region UploadOnlyFilesOnDirectory

		/// <summary>
		/// 여러 디렉토리내에서 파일만 업로드
		/// </summary>
		/// <param name="locDir"></param>
		/// <param name="remDir"></param>
		/// <param name="remote_temp_dir"></param>
		/// <param name="recursive"></param>
		public void UploadOnlyFilesOnDirectory(string locDir, string remDir, string remote_temp_dir, bool recursive)
		{
			try 
			{
				UploadOnlyFiles(locDir, "*.*", remDir, remote_temp_dir);

				if (recursive)
				{
					foreach (string directory in Directory.GetDirectories(locDir))
					{
						this.UploadOnlyFilesOnDirectory(directory, remDir, remote_temp_dir, recursive);
					}

					this.ChangeDir("..");
				}		
			}
			catch (Exception ex) 
			{
				throw ex;
			}
		}
		
		#endregion		

		#region Upload
		/// <summary>
		/// 파일 업로드
		/// </summary>
		/// <param name="fileName"></param>
		public void Upload(string fileName)
		{
			this.Upload(fileName, false, false);
		}

		/// <summary>
		/// 파일 업로드. resume지정
		/// </summary>
		/// <param name="fileName"></param>
		/// <param name="resume"></param>
		/// <param name="reName"></param>
		public void Upload(string fileName, bool resume, bool reName)
		{
			if (!this.loggedin) this.Login();

			Socket cSocket = null;
			long offset = 0;

			if (resume)
			{
				try
				{
					this.BinaryMode = true;

					offset = GetFileSize(Path.GetFileName(fileName));
				}
				catch (Exception)
				{
					// file not exist
					offset = 0;
				}
			}

			// open stream to read file
			//FileStream input = new FileStream(fileName, FileMode.Open);

			FileStream input = new FileStream(fileName, FileMode.Open, FileAccess.Read);


			if (resume && input.Length < offset)
			{
				// different file size
				offset = 0;
			}
			else if (resume && input.Length == offset)
			{
				// file done
				input.Close();
				return;
			}

			// dont create untill we know that we need it
			cSocket = this.createDataSocket();

			if (offset > 0)
			{
				this.sendCommand("REST " + offset);
				if (this.resultCode != 350)
				{
					offset = 0;
				}
			}

			this.sendCommand("STOR " + Path.GetFileName(fileName));

			if (this.resultCode != 125 && this.resultCode != 150) throw new Exception(result.Substring(4));

			if (offset != 0)
			{
				input.Seek(offset, SeekOrigin.Begin);
			}
			
			while ((bytes = input.Read(buffer, 0, buffer.Length)) > 0)
			{
				buffer = System.Text.Encoding.ASCII.GetBytes(ASCII.GetString(buffer, 0, bytes) + "<EOF>");
				cSocket.Send(buffer, bytes, 0);
			}

			input.Close();

			if (cSocket.Connected)
			{
				cSocket.Close();
			}

			this.readResponse();

			if (this.resultCode != 226 && this.resultCode != 250) throw new Exception(this.result.Substring(4));

			if (reName)
			{
				//STRIPMAP0001_019 -> STRIPMAP0001 019
				string oldFileName = Path.GetFileName(fileName);
				string newFileName = Path.GetFileName(fileName).Replace("_", " ");

				RenameFile(oldFileName, newFileName, true);
			}
		}
		#endregion

		#region UploadDirectory
		/// <summary>
		/// 디렉토리 업로드
		/// </summary>
		/// <param name="path"></param>
		/// <param name="recurse"></param>
		/// <param name="mask"></param>
		/// <param name="reName"></param>
		public void UploadDirectory(string path, bool recurse, string mask, bool reName)
		{
			string[] dirs = path.Replace("/", @"\").Split('\\');
			string rootDir = dirs[dirs.Length - 1];

			// make the root dir if it doed not exist
			if (this.GetFileList(rootDir).Length < 1)
			{
				this.MakeDir(rootDir);
			}

			this.ChangeDir(rootDir);
			

			foreach (string file in Directory.GetFiles(path, mask))
			{
				this.Upload(file, true, reName);
			}
			if (recurse)
			{
				foreach (string directory in Directory.GetDirectories(path))
				{
					this.UploadDirectory(directory, recurse, mask, reName);
				}
			}

			this.ChangeDir("..");
		}
		#endregion

		#region DeleteFile
		/// <summary>
		/// 파일 삭제
		/// </summary>
		/// <param name="fileName"></param>
		public void DeleteFile(string fileName)
		{
			try 
			{
				this.sendCommand("DELE " + fileName);
				if (this.resultCode != 250) 
				{
					throw new Exception(this.result.Substring(4));
				}
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}
		#endregion

		#region RenameFile
		/// <summary>
		/// 파일명 변경
		/// </summary>
		/// <param name="oldFileName"></param>
		/// <param name="newFileName"></param>
		/// <param name="overwrite"></param>
		public void RenameFile(string oldFileName, string newFileName, bool overwrite)
		{
			try 
			{
				this.sendCommand("RNFR " + oldFileName);
				if (this.resultCode != 350) 
				{
					throw new Exception(this.result.Substring(4));
				}

				if (!overwrite && this.GetFileList(newFileName).Length > 0) 
				{
					throw new Exception("File already exists");
				}

				if (overwrite && this.GetFileList(newFileName).Length > 0) 
				{
					this.DeleteFile(newFileName);
				}

				this.sendCommand("RNTO " + newFileName);
				if (this.resultCode != 250) 
				{
					throw new Exception(this.result.Substring(4));					
				}				
			}
			catch (Exception ex) 
			{
				throw ex;
			}
		}
		#endregion		

		#region MakeDir
		/// <summary>
		/// 디렉토리 생성
		/// </summary>
		/// <param name="dirName"></param>
		public void MakeDir(string dirName)
		{
			try 
			{
				this.sendCommand( "MKD " + dirName );

				if (this.resultCode != 250 && this.resultCode != 257) 
				{
					throw new Exception(this.result.Substring(4));
				}
			}
			catch (Exception ex) 
			{
				throw ex;
			}
		}
		#endregion

		#region RemoveDir
		/// <summary>
		/// 디렉토리 삭제
		/// </summary>
		/// <param name="dirName"></param>
		public void RemoveDir(string dirName)
		{
			try 
			{
				this.sendCommand("RMD " + dirName);

				if (this.resultCode != 250) 
				{
					throw new Exception(this.result.Substring(4));
				}
			}
			catch (Exception ex) 
			{
				throw ex;
			}
		}
		#endregion

		#region ChangeDir
		/// <summary>
		/// 디렉토리 정보 변경
		/// </summary>
		/// <param name="dirName"></param>
		public void ChangeDir(string dirName)
		{
			try 
			{
				if (dirName != null && !dirName.Equals(".") && dirName.Length != 0)
				{
					this.sendCommand("CWD " + dirName);
					if (this.resultCode != 250) 
					{
						throw new Exception(this.result.Substring(4));
					}
				}				
			}
			catch (Exception ex) 
			{
				throw ex;
			}
		}
		#endregion
		
		#endregion

		#region Socket
		/// <summary>
		/// 소켓 생성
		/// </summary>
		/// <returns>Connected socket</returns>
		private Socket createDataSocket()
		{
			string ipData;
			string buf;
			string ipAddress;

			int index1;
			int index2;
			int len;
			int partCount;
			int portNr;

			int[] parts;

			Socket socket = null;
			IPEndPoint ipEndPoint = null;

			try 
			{
				this.sendCommand("PASV");
				if (this.resultCode != 227) 
				{
					throw new Exception(this.result.Substring(4));
				}

				#region Getting PASV mode IP/Port

				index1 = this.result.IndexOf('(');
				index2 = this.result.IndexOf(')');			
				partCount = 0;

				ipData = this.result.Substring(index1+1,index2-index1-1);				
				len = ipData.Length;
				buf = "";

				parts = new int[6];

				for (int i=0;(i<len && partCount<=6);i++)
				{
					char ch = char.Parse(ipData.Substring(i,1));

					if (char.IsDigit(ch)) 
					{
						buf = buf + ch;
					}						
					else if (ch != ',') 
					{
						throw new Exception("Malformed PASV result: " + result);
					}					

					if (ch == ',' || i+1 == len)
					{
						try
						{
							parts[partCount++] = int.Parse(buf);
							buf = "";
						}
						catch (Exception ex)
						{
							throw new Exception("Malformed PASV result (not supported?): " + this.result, ex);
						}
					}
				}

				ipAddress = parts[0] + "."+ parts[1]+ "." + parts[2] + "." + parts[3];
				portNr = (parts[4] << 8) + parts[5];

				#endregion

				socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
				ipEndPoint = new IPEndPoint(Dns.GetHostEntry(ipAddress).AddressList[0], portNr);

				socket.Connect(ipEndPoint);

			}
			catch (Exception ex) 
			{
				if (socket != null) 
				{
					if (socket.Connected) 
					{
						socket.Shutdown(SocketShutdown.Both);
						socket.Close();
					}
					socket = null;
				}

				throw ex;
			}

			return socket;
		}

		/// <summary>
		/// 명령어 전송
		/// </summary>
		/// <param name="command"></param>
		private void sendCommand(string command)
		{
			try 
			{
				Byte[] cmdBytes = Encoding.ASCII.GetBytes((command + "\r\n").ToCharArray());
				ftpClientSocket.Send(cmdBytes, cmdBytes.Length, 0);			
				this.readResponse();
			}
			catch (Exception ex) 
			{
				throw ex;
			}
		}

		/// <summary>
		/// 응답 확인
		/// </summary>
		private void readResponse()
		{
			try 
			{
				this.message = "";
				this.result = this.readLine();

				if (this.result.Length > 3) 
				{
					this.resultCode = int.Parse(this.result.Substring(0,3));
				}					
				else 
				{
					this.result = null;
				}					
			}
			catch (Exception ex)
			{
				throw ex;
			}
		}

		private string readLine()
		{
			try 
			{
				while (true)
				{
					this.bytes = ftpClientSocket.Receive(this.buffer, this.buffer.Length, 0);
					this.message = this.message + ASCII.GetString(this.buffer, 0, this.bytes);

					if (this.bytes < this.buffer.Length)
					{
						break;
					}
				}

				string[] msg = this.message.Split('\n');

				if (this.message.Length > 2) 
				{
					this.message = msg[msg.Length - 2];
				}
				else 
				{
					this.message = msg[0];
				}

				if (this.message.Length > 4 && !this.message.Substring(3,1).Equals(" ")) 
				{
					return this.readLine();
				}
			}
			catch (Exception ex) 
			{
				throw ex;
			}

			return message;
		}
		#endregion

		#region Destructor
		/// <summary>
		/// ~FtpClient
		/// </summary>
		~FtpClient()
		{
			this.Close();
		}
		#endregion
	}
}
